local desc = [[
# 红心大战玩法简介

Windows上的经典小游戏，但是大家应该很少玩过PVP版本的吧。不对，应该玩过这款的
都没有多少位呢。总之以下是玩法介绍，但是将规则语言变成了类似三国杀风格的。

## 游戏流程

游戏以去掉大小王的52张扑克为牌堆，4人进行对局，轮次为基本流程。一轮的流程如下：

1. 一轮开始时，洗牌并发给每人13张。
2. 每名玩家同时选择3张牌并交给另一名玩家。在第一轮，这名玩家是你的下家；
   第二轮是你的上家；第三轮是坐在你对面的玩家；第四轮不给牌；第五轮重新开始循环。
3. 完成换牌后，以回合为顺序进行游戏。回合的流程：
   * 若为第一回合，则由持有梅花2的玩家开始出牌；否则从上回合中出了最大牌的玩家开始。
   * 在一回合中，各个玩家需要依次出一张牌。所有玩家一共出完四张牌后，比较其大小，
     牌最大的角色获得本回合中的四张牌并负责下回合第一个出牌。
   * 首先出牌的玩家可以任意出牌，但不能出红桃牌，除非本局游戏中已经有玩家出过红桃牌；
     若为第一回合，则首先出牌的玩家只可以出梅花2
   * 其他跟牌的玩家必须跟一张花色相同的牌，除非手中已经没有花色不同的牌；
     若为第一回合，则不可跟牌红桃牌或者黑桃Q，除非手中所有牌只有红桃牌或黑桃Q。
4. 当回合结束后，若所有玩家均没有牌可出，则进行计分并进行下一轮。若有玩家达到100分，则游戏结束。
   得分最少的玩家获胜。

牌的大小计算规则为：首先比较花色，本回合第一张牌的花色为大花色；然后比较点数，
2是最小的点数，然后直到K，A是最大的点数。

当玩家获得牌时，每有一张红桃牌，就会为该玩家加1分；若获得黑桃Q，则加13分。

每轮结束计分时，若有玩家本轮获得26分（即收取所有计分的牌），则该玩家改为加0分，
其他3位玩家各加26分。

### 其他

排行榜以身份标记出。主公为目前得分最少者（获胜者），其余依此类推。
]]

---@param c1 Card
---@param c2 Card
local function heartsLarger(c1, c2)
  local n1, n2 = c1.number, c2.number
  if n1 == 1 then n1 = 100 end
  if n2 == 1 then n2 = 100 end
  return n1 > n2
end

---@param room Room
---@param starter ServerPlayer
---@return ServerPlayer
local turn = function(room, starter)
  local player = starter
  local players = {}
  local winner = player
  local cards_played = {}
  local isFirstTurn = starter:getHandcardNum() == 13
  for _, p in ipairs(room.players) do
    if p == starter then
      room:setPlayerProperty(p, "phase", Player.Play)
    else
      room:setPlayerProperty(p, "phase", Player.NotActive)
    end
  end

  for _ = 1, 4 do
    local pattern = "."
    local prompt = "#hearts-play"
    local firstCard = cards_played[1] and Fk:getCardById(cards_played[1]) or nil
    if not firstCard then
      -- 此时出第一张牌，第一回合的话只能梅花2，否则需要判断红桃
      if isFirstTurn then
        pattern = ".|2|club"
      else
        if room:getTag("HeartPlayed") or table.every(player.player_cards[Player.Hand], function(cid)
          return Fk:getCardById(cid).suit == Card.Heart
        end) then
          pattern = "."
        else
          pattern = ".|.|^heart"
        end
      end
    else
      -- 需要跟牌，若有同花色则必须同花色，否则按是否第一回合特判
      if table.find(player.player_cards[Player.Hand], function(cid)
        return Fk:getCardById(cid).suit == firstCard.suit
      end) then
        pattern = ".|.|" .. firstCard:getSuitString()
        prompt = "#hearts-play-suit:::" .. firstCard:getSuitString()
      else
        if isFirstTurn and table.find(player.player_cards[Player.Hand], function(cid)
          local card = Fk:getCardById(cid)
          return card.suit ~= Card.Heart and not (card.suit == Card.Spade and card.number == 12)
        end) then
          pattern = ".|.|club,diamond;.|^12|spade"
        else
          pattern = "."
        end
      end
    end
    local card = room:askForCard(player, 1, 1, false, "", false, pattern, prompt)
    table.insert(cards_played, card[1])
    room:moveCardTo(card, Card.Processing)
    room:sendFootnote(card, {
      type = firstCard and "##HeartsRespond" or "##HeartsPlay",
      from = player.id,
    })
    room:sendLog{
      type = "#HeartsPlayCard",
      from = player.id,
      card = card,
    }
    if Fk:getCardById(card[1]).suit == Card.Heart then
      room:setTag("HeartPlayed", true)
    end
    table.insert(players, player)
    player = player.next
  end

  local firstCard = Fk:getCardById(cards_played[1])
  local bigSuit = firstCard.suit
  for i, p in ipairs(players) do
    local c = Fk:getCardById(cards_played[i])
    if c.suit == bigSuit then
      if heartsLarger(c, firstCard) then
        firstCard = c
        winner = p
      end
    end
  end

  room:animDelay(1)

  winner:addToPile("hearts_game_pile", cards_played, true)
  local score = winner:getMark("@[heartsgame]")
  for _, id in ipairs(cards_played) do
    local card = Fk:getCardById(id)
    if card.suit == Card.Heart then
      score[2] = score[2] + 1
    elseif card.suit == Card.Spade and card.number == 12 then
      score[2] = score[2] + 13
    end
  end
  room:setPlayerMark(winner, "@[heartsgame]", score)
  return winner
end

---@param room Room
local round = function(room)
  local roundCount = room:getTag("RoundCount") or 0
  roundCount = roundCount + 1
  room:setTag("RoundCount",  roundCount)
  room:doBroadcastNotify("UpdateRoundNum", roundCount)
  room:setTag("HeartPlayed", nil)

  local players = table.simpleClone(room.players)
  for _, p in ipairs(players) do
    room:throwCard(p:getPile("hearts_game_pile"), nil, p, p)
  end
  room:shuffleDrawPile()
  for _, p in ipairs(players) do
    p:drawCards(13)
  end

  local giveType = roundCount % 4
  if giveType ~= 0 then
    local tab
    if giveType == 1 then
      tab = {
        [players[1]] = players[2],
        [players[2]] = players[3],
        [players[3]] = players[4],
        [players[4]] = players[1],
      }
    elseif giveType == 2 then
      tab = {
        [players[1]] = players[4],
        [players[2]] = players[1],
        [players[3]] = players[2],
        [players[4]] = players[3],
      }
    else
      tab = {
        [players[1]] = players[3],
        [players[2]] = players[4],
        [players[3]] = players[1],
        [players[4]] = players[2],
      }
    end
    local req = Request:new(players, "AskForUseActiveSkill")
    for _, p in ipairs(players) do
      req:setData(p, {"choose_cards_skill", "#hearts-give-round" .. giveType, false, {
        num = 3,
        min_num = 3,
        include_equip = false,
        pattern = ".",
      }})
      req:setDefaultReply(p, { card = { subcards = table.random(p.player_cards[Player.Hand], 3) } })
    end
    req:ask()
    for _, p in ipairs(players) do
      local cards = req:getResult(p).card.subcards
      room:moveCardTo(cards, Card.PlayerHand, tab[p], fk.ReasonGive, "", nil, false)
    end
    room:animDelay(1)
  end

  local starter = table.find(players, function(p)
    return table.find(p.player_cards[Player.Hand], function(cid)
      local card = Fk:getCardById(cid)
      return card.suit == Card.Club and card.number == 2
    end) ~= nil
  end)

  while true do
    starter = turn(room, starter)
    if starter:isKongcheng() then break end
  end

  for _, p in ipairs(players) do
    local score = p:getMark("@[heartsgame]")
    if score[2] == 26 then
      for _, p2 in ipairs(room:getOtherPlayers(p)) do
        local s = p2:getMark("@[heartsgame]")
        s[1] = s[1] + 26
        room:setPlayerMark(p2, "@[heartsgame]", s)
      end
      room:sendLog{
        type = "#HeartsAllIn",
        arg = p._splayer:getScreenName(),
        toast = true,
      }
      score[2] = 0
    end
    score[1] = score[1] + score[2]
    score[2] = 0
    room:setPlayerMark(p, "@[heartsgame]", score)
  end

  table.sort(players, function(p1, p2)
    return p1:getMark("@[heartsgame]")[1] > p2:getMark("@[heartsgame]")[1]
  end)
  local scores = table.map(players, function(p) return p:getMark("@[heartsgame]")[1] end)
  room:setPlayerProperty(players[1], "role", "renegade")
  room:setPlayerProperty(players[2], "role", scores[2] == scores[4] and "lord" or "rebel")
  room:setPlayerProperty(players[3], "role", scores[3] == scores[4] and "lord" or "loyalist")
  room:setPlayerProperty(players[4], "role", "lord")
  if players[1]:getMark("@[heartsgame]")[1] > room:getBanner("@HeartsGoal") then
    room:setPlayerProperty(players[1], "dead", true)
    room:sendLog{
      type = "#HeartsGameOver",
      arg = players[4]._splayer:getScreenName(),
      toast = true,
    }
    room:gameOver("lord")
  end
end

---@param room Room
local game = function(room)
  room.current = room.players[1]
  local choice = room:askToChoice(room.current, {
    choices = { "50", "75", "100" },
    skill_name = "",
    prompt = "#hearts-rule-select",
  })
  room:setBanner("@HeartsGoal", tonumber(choice))
  for _, p in ipairs(room.players) do
    room:setPlayerMark(p, "@[heartsgame]", { 0, 0 })
  end
  room:animDelay(1)
  while true do round(room) end
end

local logic = function()
  ---@class HeartLogic: GameLogic
  local heartlogic = GameLogic:subclass("HeartLogic")
  function heartlogic:chooseGenerals()
    local room = self.room
    for _, p in ipairs(room.players) do
      local avatar = p._splayer:getAvatar()
      if avatar == "anjiang" then avatar = table.random{ "blank_shibing", "blank_nvshibing" } end
      local avatar_general = Fk.generals[avatar] or Fk.generals["sunce"] or Fk.generals["diaochan"]
      room:setPlayerProperty(p, "general", avatar_general.name)
      room:setPlayerProperty(p, "kingdom", avatar_general.kingdom)
      room:setPlayerProperty(p, "role_shown", true)
    end
  end
  function heartlogic:broadcastGeneral() end
  function heartlogic:attachSkillToPlayers() end
  function heartlogic:prepareForStart() end
  function heartlogic:action()
    game(self.room)
  end
  return heartlogic
end

Fk:addQmlMark{
  name = "heartsgame",
  how_to_show = function(name, value, player)
    return Fk:translate("hearts_score_board"):format(value[1], value[2])
  end,
  qml_path = "",
}

local mode = fk.CreateGameMode {
  name = "hearts_game",
  minPlayer = 4,
  maxPlayer = 4,
  logic = logic,
  whitelist = function(self, pkg)
    if pkg.type ~= Package.CardPack then return true end
    return pkg.name == "poker-deck"
  end,
}

Fk:loadTranslationTable {
  ["hearts_game"] = "红心大战",
  ["@[heartsgame]"] = "",
  [":hearts_game"] = desc,
  ["hearts_score_board"] = "%d分 本轮+%d",
  ["hearts_game_pile"] = "已收取",

  ["#hearts-rule-select"] = "规则：请选择结束游戏所需的最高分数",
  ["#hearts-give-round1"] = "选择要交给下家的三张牌，并点确定",
  ["#hearts-give-round2"] = "选择要交给上家的三张牌，并点确定",
  ["#hearts-give-round3"] = "选择要交给对面玩家的三张牌，并点确定",
  ["#hearts-play"] = "请出一张牌。",
  ["#hearts-play-suit"] = "请出一张%arg牌。",

  ["@HeartsGoal"] = "目标累积分数",
  ["#HeartsAllIn"] = "玩家 %arg 本轮达成“全收”！改为其他三名玩家各加26分",
  ["#HeartsGameOver"] = "游戏结束。恭喜玩家 %arg 获胜！",
  ["#HeartsPlayCard"] = "%from 出牌: %card",
  ["##HeartsPlay"] = "%from出牌",
  ["##HeartsRespond"] = "%from跟牌",
}

return mode
